package server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;

import iface.Envelope;
import iface.HvZUser;
import iface.Location;
import iface.Logger;
import iface.NetworkConstants;
import iface.UserTypes;

public class HvZMainServer {
    private static final long CACHE_CLEANER_DELAY_SECONDS = NetworkConstants.REMOVE_STALE_CACHED_USER
            / HvZUser.MAX_FRESHNESS;
    private static Map<UserCache.CacheType, UserCache> caches;
    private static final boolean DEBUG = true;

    public static void main(String args[]) {
        // Create initial human and zombie caches.
        caches = new HashMap<UserCache.CacheType, UserCache>();
        caches.put(UserCache.CacheType.HUMAN, new UserCache(
                UserCache.CacheType.HUMAN));
        caches.put(UserCache.CacheType.ZOMBIE, new UserCache(
                UserCache.CacheType.ZOMBIE));

        // Start the database.
        Database.getInstance();

        if (DEBUG)
            setupDebugUsers();

        // Start cache timer for human & zombie caches.
        setupCacheTimer();

        // Create/open TCP listener
        ServerSocket serverSocket;
        try {
            serverSocket = new ServerSocket(NetworkConstants.SERVER_PORT);
        } catch (IOException e) {
            Logger.error(e.toString());
            Logger.error("Unable to create server socket, exiting.");
            System.exit(-1);
            return;
        }
        Logger.log("Listening on port: " + NetworkConstants.SERVER_PORT);

        // Start loop to accept on incoming connections.
        while (true) {
            Socket clientSocket;
            try {
                clientSocket = serverSocket.accept();
            } catch (IOException e) {
                Logger.error("Unable to accept on incoming connection.");
                continue;
            }
            Logger.verbose("Received connection from: "
                    + clientSocket.getInetAddress());

            ObjectOutputStream outStream;
            ObjectInputStream inStream;
            Object recvObj;
            try {
                outStream = new ObjectOutputStream(
                        clientSocket.getOutputStream());
                inStream = new ObjectInputStream(clientSocket.getInputStream());
                recvObj = inStream.readObject();
            } catch (IOException e) {
                Logger.error(e.toString());
                Logger.error("IOError with an object stream, dropping this client connection.");
                continue;
            } catch (ClassNotFoundException e) {
                Logger.error(e.toString());
                Logger.error("Error with class, dropping this client connection.");
                continue;
            }

            if (recvObj instanceof Envelope) {
                ConnectionState connState = new ConnectionState(clientSocket,
                        inStream, outStream);
                new HvZDispacher(connState, (Envelope) recvObj, caches).start();
            } else {
                Logger.error("Unable to decode incoming message as an envelope from: "
                        + clientSocket.getInetAddress());
            }
        }
    }

    /*
     * Create a human and a zombie that last forever and are never evicted from
     * their respective user caches.
     */
    private static void setupDebugUsers() {
        UserCache humanCache = caches.get(UserCache.CacheType.HUMAN);
        Location loc1 = new Location("human-forever", -124.30554333333333,
                49.654521666666675);
        HvZUser test1 = new HvZUser("human-forever", UserTypes.HUMAN,
                "222-222-2222", loc1);
        test1.keepForever();
        humanCache.addUser(test1);

        UserCache zombieCache = caches.get(UserCache.CacheType.ZOMBIE);
        Location loc2 = new Location("zombie-forever", -124.30554333333333,
                49.654521666666675);
        HvZUser test2 = new HvZUser("zombie-forever", UserTypes.ZOMBIE,
                "222-222-1111", loc2);
        test2.keepForever();
        zombieCache.addUser(test2);
    }

    /*
     * Setup the timer to clean the cache
     */
    private static void setupCacheTimer() {
        Timer timer = new Timer();
        long delay = CACHE_CLEANER_DELAY_SECONDS * 1000;
        timer.schedule(new HvZCacheCleaner(caches), 0, delay);
    }

}
